/*********************************************************************
/* Java Anything Implementation as universal Data Access Object
/* ALang - a simple Lisp like interpreter as an Anything example
/*
/* Copyright (C) 2006 Stefan Tramm
/* 
/* This library is free software; you can redistribute it and/or
/* modify it under the terms of the GNU Lesser General Public
/* License as published by the Free Software Foundation; either
/* version 2.1 of the License, or (at your option) any later version.
/* 
/* This library is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
/* Lesser General Public License for more details.
/* 
/* You should have received a copy of the GNU Lesser General Public
/* License along with this library; if not, write to the Free Software
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
/* MA  02110-1301  USA
/********************************************************************/

import static any.functor.Functions.asLong;

import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;

import any.Any;


/* implement the core of a simple Lisp like interpreter
 * using Anything as n-tupel implementation
 */
public class ALang {

    Any env;
    Any stats;
    Any prims;

    public interface PrimitiveImpl {
        public Any impl(Any list, Any assoc);
    }
    
    public ALang() {
        resetbindings();
        resetprimitives();
    }

    // do NOT modify these two Anythings
    private static Any nil = (Any)Any.create.any().toImmutable();
    private static Any t = a("t");

    private static Any a(String s) {
        return Any.create.any(s);
    }
    
    private static Any a(long l) {
        return Any.create.any(l);
    }

    // primitives
    Any t() {
        return t;
    }
    
    Any nil() {
        return nil;
    }

    Any atom(Any l) {
        if (l.isSimpleType()) return t;
        return nil;
    }
    
    Any eq(Any x, Any y) {
        if (atom(x).equals(t) && atom(y).equals(t) && x.equals(y)) return t;
        return nil;
    }
    
    Any car(Any l) {
        if (atom(l).equals(t)) return nil;
        return l.getSet(0);
    }
    
    Any cdr(Any l) {
        if (atom(l).equals(t)) return nil;
        return l.getSet(1);
    }

    Any cons(Any x, Any y) {
        Any l = Any.create.any();
        l.set(0, x).set(1, y);
        return l;
    }
    
    Any cond(Any c, Any a) {
        while (atom(c)!=t) {
            if (eval(caar(c), a).equals(t)) return eval(cadar(c), a);
            c = cdr(c);
        }
        return nil;
    }
    
    Any isnil(Any x) {
        return eq(x, nil);
    }
    
    // test whether an Any is a proper list
    private boolean isList(Any x) {
        if (x.equals(nil))           return true;
        if (atom(x).equals(t))       return false;
        if (x.size()!=2)      return false;
        if (cdr(x).equals(nil))      return true;
        if (atom(cdr(x)).equals(t))  return false;
        if (cdr(x).size()!=2) return false;
        return true;
    }

    // the evaluator
    public void resetbindings() {
        env = list();
        stats = Any.create.any();
    }
    public void registerprim(String name, PrimitiveImpl impl) {
        prims.put(name, impl);
    }
    private Any call(String name, Any list, Any assoc) {
        PrimitiveImpl p = (PrimitiveImpl)(prims.getPut(name).toObject());
        return p.impl(list, assoc);
    }

    private void updateStats(String k) {
        long n = stats.getPut(k).give(asLong().withDefault(0));
        stats.put(k, n+1);
    }

    public void bind(Any sym, Any l) {
        env = cons(list(sym, l), env);
    }

    public Any eval(Any e, Any a) {
        System.out.println("eval: "+pp(e)+" assoc: "+pp(a));
        updateStats("eval");
        if (atom(e).equals(t)) { // make a name lookup for strings, longs and doubles as is
        	Class<?> type = e.getType();
            if (type.equals(Number.class))
                return e;
            return assoc(e, a);
        } else if (atom(car(e)).equals(t)) { // invoke primitive
            if (car(e).equals(nil)) {
                System.out.println("error: eval nil, returns ()");
                return nil;
            }
            String f = car(e).asString("");
            updateStats(f);
            if (f.equals("quote"))  return cadr( e);
            if (f.equals("atom"))   return atom( eval(cadr(e),  a));
            if (f.equals("eq"))     return eq(   eval(cadr(e),  a),
                                                 eval(caddr(e), a));
            if (f.equals("car"))    return car(  eval(cadr(e),  a));
            if (f.equals("cdr"))    return cdr(  eval(cadr(e),  a));
            if (f.equals("cons"))   return cons( eval(cadr(e),  a),
                                                 eval(caddr(e), a));
            if (f.equals("cond"))   return evcon(cdr(e),        a);
            if (prims.containsKey(f)) return call( f, evlis(cdr(e), a), a);
            // else replace symbol with assoc
            return eval(cons(assoc(car(e), a), cdr(e)), a);
        } else if (caar(e).asString("").equals("label")) {
            updateStats("label");
            return eval(cons(caddar(e), cdr(e)),
                        cons(list(cadar(e), car(e)), a));
        } else if (caar(e).asString("").equals("lambda")) {
            updateStats("lambda");
            return eval(caddar(e),
                        append(pair(cadar(e),
                                    evlis(cdr(e), a)),
                               a));
        }
        return nil;
    }
    
    Any evcon(Any c, Any a) {
        do {
            if (eval(caar(c), a).equals(t)) return eval(cadar(c), a);
            c = cdr(c);
        } while (atom(c)!=t);
        return nil;
    }
    
    Any evlis(Any m, Any a) { // recursive
        if (m.equals(nil)) return nil;
        return cons(eval(car(m), a), evlis(cdr(m), a));
    }
    
    Any assoc(Any x, Any y) {
        do {
            if (eq(caar(y), x).equals(t))
                return cadar(y);
            y = cdr(y);
        } while (atom(y)!=t);
        return nil;
    }
    
    // convenience functions, needed for eval
    Any caar(Any x) {
        return car(car(x));
    }
    Any cadr(Any x) {
        return car(cdr(x));
    }
    Any cddr(Any x) {
        return cdr(cdr(x));
    }
    Any caddr(Any x) {
        return car(cdr(cdr(x)));
    }
    Any cadar(Any x) {
        return car(cdr(car(x)));
    }
    Any cadddr(Any x) {
        return car(cdr(cdr(cdr(x))));
    }
    Any caddar(Any x) {
        return car(cdr(cdr(car(x))));
    }
    Any append(Any x, Any y) { // recursive
        if (x.equals(nil)) return y;
        return cons(car(x), append(cdr(x), y));
    }
    Any pair(Any x, Any y) { // recursive
        if (x.equals(nil) && y.equals(nil)) return nil;
        if (atom(x)!=t && atom(y)!=t)
            return cons(list(car(x), car(y)),
                        pair(cdr(x), cdr(y)));
        return nil;
    }

    // printer and parser
    String pp(Any l) {
        StringBuffer sb = new StringBuffer();
        pp1(sb, l);
        String s = new String(sb);
        //System.out.println("pp: "+s);
        return s;
    }
    private void pp1(StringBuffer s, Any l) {
        if (l.equals(nil)) { s.append("()");  // nil
        } else if (atom(l).equals(t)) {       // atom
            String a = l.asString("{}");
            if (a.indexOf(' ') >= 0) {
                a.replaceAll("\"", "\\\"");
                a = '"'+a+'"';
            }
            s.append(a);
        } else if (isList(l)) {      // list
            // special (quote ...) handling
            if (car(l).asString("").equals("quote") && (cddr(l).equals(nil))) {
                s.append('\'');
                pp1(s, cadr(l));
            } else {
                s.append('(');
                // check for pair or list,  && (cdr(l).equals(nil) || atom(cdr(l))!=t)
                // do while loop?
                do {
                    pp1(s, car(l));
                    l = cdr(l);
                    if (l.equals(nil)) break; // end of list
                    s.append(' ');
                    if (!isList(l)) {  // last is not a list
                        pp1(s, l);
                        break;
                    }
                } while (true);
                s.append(')');
            }
        } else {                       // tupel, dict
            String slot = null;
            int i = 0;
            s.append('{');
            do {
                if (i > 0) s.append(' ');
                if ((slot = l.getKeyForIndex(i)) != null) {
                    s.append('/');
                    s.append(slot);
                    s.append(' ');
                }
                pp1(s, l.getSet(i));
            } while (++i < l.size());
            s.append('}');
        }
    }
    
    public Any parse(String s) {
        StringReader reader = new StringReader(s);
        return parse(reader);
    }
    public Any parse(Reader r) {
        if (r == null) return nil;

        StreamTokenizer t = new StreamTokenizer(r);

        t.slashSlashComments(true);
        t.wordChars('_', '_');
        t.quoteChar('\"');
        t.commentChar(';');
        t.ordinaryChar('\'');
        t.ordinaryChar('(');
        t.ordinaryChar(')');
        t.ordinaryChar('{');
        t.ordinaryChar('}');
        //t.ordinaryChar('/'); // key
        t.parseNumbers(); // cannot be switched of, is turned on by default

        return parse1(t);
    }
    private Any parse1(StreamTokenizer t) {
        int tok = StreamTokenizer.TT_EOF;

        try {
            tok = t.nextToken();
        } catch (IOException e) {
            System.out.println("IOException");
        }

        switch (tok) {
            case StreamTokenizer.TT_EOF:
                break;
            case StreamTokenizer.TT_NUMBER: // atom number
                double di = java.lang.Math.rint(t.nval);
                if (di == t.nval) {
                    // double is an integer
                    Double d = new Double(t.nval);
                    return a(d.longValue());
                } else {
                    // 'real' double
                    return Any.create.any(t.nval);
                }
            case StreamTokenizer.TT_WORD:  // atom sym
                if (t.sval.equals("t"))
                    return t();            // handle the t symbol
                // fall through
            case '\"':                     // atom string
                return a(t.sval);
            case '{':                      // tupel
                Any a = Any.create.any();
                parsetupel(a, t);
                return a;
            case '(':                      // list
                return parselist1(t);
            case '\'':                     // special (quote ...) handling
                return list(a("quote"), parse1(t));
            default:
                System.out.println("wrong character: " + tok);
            break;
        }
        return Any.create.any();
    }

    private void parsetupel(Any a, StreamTokenizer t) {
        try {
            int tok;
            int index = 0;
            while (true) {
                tok = t.nextToken();
                if (tok == '}' || tok == StreamTokenizer.TT_EOF) {
                    break;
                }
                t.pushBack();
                a.set(index++, parse1(t));
            }
        } catch (IOException e) {
            System.out.println("IOException");
        }
    }

    private Any parselist1(StreamTokenizer t) {
        try {
            int tok = t.nextToken();
            if (tok == ')' || tok == StreamTokenizer.TT_EOF) {
                  return nil;
            }
            t.pushBack();
            Any l = Any.create.any();
            parselist2(l, t);
            return l;
        } catch (IOException e) {
            System.out.println("IOException");
        }
        return nil;
    }

    private void parselist2(Any x, StreamTokenizer t) {
        try {
            while (true) {
                x.set(0, parse1(t));
                int tok = t.nextToken();
                if (tok == ')' || tok == StreamTokenizer.TT_EOF) {
                  x.set(1,nil);
                  return;
                }
                t.pushBack();
                x = x.getSet(1);
            }
        } catch (IOException e) {
            System.out.println("IOException");
        }
    }

    // more convenience functions
    Any list() {
        return nil;
    }
    Any list(Any l) {
        return cons(l, list());
    }
    Any list(Any l, Any ll) {
        return cons(l, list(ll));
    }
    Any list(Any l, Any ll, Any lll) {
        return cons(l, list(ll, lll));
    }
    Any list(Any l, Any ll, Any lll, Any llll) {
        return cons(l, list(ll, lll, llll));
    }
    Any list(Any l, Any ll, Any lll, Any llll, Any l5) {
        return cons(l, list(ll, lll, llll, l5));
    }
    Any list(Any l, Any ll, Any lll, Any llll, Any l5, Any l6) {
        return cons(l, list(ll, lll, llll, l5, l6));
    }

    Any tupel2list(Any t) {
        // not needed: if (atom(t).equals(t)) return nil;
        Any l = nil;
        for (int i=t.size()-1; i>=0; i--)
            l = cons(t.getSet(i), l);
        return l;
    }
    
    // the main interpreter
    public String interprete(String expr) {
        stats = Any.create.any();
        Any res = list();
        Any e = parse(expr);
        System.out.println("interprete: "+pp(e));
        if (atom(e).equals(t)) {
            res = eval(e, env);
        } else if (atom(car(e)).equals(t)) { // invoke primitive
            String f = car(e).asString("");
            res = cadr(e); // result is name of variable or function
            if (f.equals("setq")) {
                bind(cadr(e), eval(caddr(e), env));
            } else if (f.equals("set")) {
                bind(eval(cadr(e), env), eval(caddr(e), env));
            } else if (f.equals("defun")) {
                bind(cadr(e),
                     list(a("label"), cadr(e),
                     list(a("lambda"), caddr(e), cadddr(e))));
            } else { // nothing special, so eval it
                res = eval(e, env);           
            }
        } else {
            res = a(">>> ");
        }
        System.out.println("--> "+pp(res));
        System.out.println("runtime stats: "+pp(stats));
        return pp(res);
    }
    
    // extend here...
    public void resetprimitives() {
        prims = Any.create.any();
        registerprim("null", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return isnil(car(list));
            }
        });
        registerprim("caar", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return caar(list);
            }
        });
        registerprim("cadr", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return cadr(list);
            }
        });
        registerprim("cddr", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return cddr(list);
            }
        });
        registerprim("cadar", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return cadar(list);
            }
        });
        registerprim("caddr", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return caddr(list);
            }
        });
        registerprim("caddar", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return caddar(list);
            }
        });
        registerprim("cadddr", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return cadddr(list);
            }
        });
        registerprim("listp", new PrimitiveImpl() {
            @SuppressWarnings("synthetic-access")
            public Any impl(Any list, Any assoc) {
                if (isList(car(list))) return t;
                return nil;
            }
        });
        registerprim("append", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return append(cadr(list), caddr(list));
            }
        });
        registerprim("pair", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                return pair(cadr(list), caddr(list));
            }
        });
        registerprim("tupel2list", new PrimitiveImpl() {
            public Any impl(Any list, Any assoc) {
                // assert is tupel
                return tupel2list(list);
            }
        });
        registerprim("plus", new PrimitiveImpl() {
            @SuppressWarnings("synthetic-access")
            public Any impl(Any list, Any assoc) {
                long sum = 0;
                while (!list.equals(nil)) {
                    sum += car(list).give(asLong().withDefault(999999)); // magic value
                    list = cdr(list);
                }
                return a(sum);
            }
        });
    }

}
